//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//==========================================================================
// Copyright (c) 2000-2004,  Elastos, Inc.  All Rights Reserved.
//**author:hanxu
//**y/m/d:2005/5/16
//==========================================================================

#include "pcivar.h"
#include "pcireg.h"

#include <bonito.h>
#include <bsp.h>
#include <stdarg.h>

#include "pci.h"

#ifndef dimof
#define dimof(arr)	(sizeof(arr)/sizeof(arr[0]))
#endif

#ifndef MIN
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#endif

#ifndef MAX
#define MAX(a,b)	((a) > (b) ? (a) : (b))
#endif


// pci_devinfo uses sprintf(), and we don't necessarily want to drag in
// all those tables for a minimal SDE-MIPS build, so set this function
// pointer if it is required.
void	(*_pci_devinfo_func) (unsigned int, unsigned int, char *, int *);

int	_pci_nbus = 1;		// start off with one bus
int	_pci_maxbus = 0;		// maximum number of busses
int	_pci_enumerated;

#define PCIMAX_DEV	16	// arbitrary
#define PCIMAX_MEMWIN	3	// arbitrary per device
#define PCIMAX_IOWIN	1	// arbitrary per device

struct pcidev {
    struct pci_attach_args pa;
    unsigned char	min_gnt;
    unsigned char	max_lat;
    short		nmemwin;
    short		niowin;
};

struct pciwin {
    struct pcidev *dev;
    int		reg;
    int	size;
    unsigned int	address;
};
//all of these is the static variable
static struct pcidev pcidevs[PCIMAX_DEV];
static unsigned int pcindev;

static struct pciwin pcimemwin[PCIMAX_DEV * PCIMAX_MEMWIN];
static unsigned int pcinmemwin;

static struct pciwin pciiowin[PCIMAX_DEV * PCIMAX_IOWIN];
static unsigned int pciniowin;

//===============================================================

extern unsigned int _pci_make_tag(int bus, int device, int function);
extern void _pci_break_tag(unsigned int tag, int *busp, int *devicep, int *functionp);
extern int _pci_canscan (unsigned int tag);
extern unsigned int _pci_conf_read(unsigned int tag, int reg);
extern void _pci_conf_write(unsigned int tag, int reg, unsigned int data);
extern unsigned int _pci_allocate_io (unsigned int tag, unsigned long  size);
extern unsigned int _pci_allocate_mem (unsigned int tag, unsigned long size);

extern void _pci_devinfo(unsigned int id_reg, unsigned int class_reg, char *cp, int *supp);

extern int _pci_hwinit (int initialise);
extern void _pci_hwreinit (void);

extern  void qsort(char *bot, unsigned int nmemb, unsigned int size, int ( *compar)(struct pciwin *a, struct pciwin *b));

EXTERN_C int  CDECL vkprintf(const char *, va_list);

//------------------------------------------------------

//=============================================================
static void
print_bdf (int bus, int device, int function)
{
    kprintf ("PCI");
    if (_pci_nbus > 1 && bus >= 0)
	kprintf (" bus %d", bus);
    if (device >= 0)
	kprintf (" slot %d", device);
    if (function >= 0)
	kprintf ("/%d", function);
    kprintf (": ");
}

void
_pci_bdfprintf (int bus, int device, int function, const char *fmt, ...)
{
    va_list arg;

    print_bdf (bus, device, function);
#ifdef __VARARGS_H
    va_start(arg);
#else
    va_start(arg, fmt);
#endif
    vkprintf (fmt, arg);
    va_end(arg);
}

void
_pci_tagprintf (unsigned int tag, const char *fmt, ...)
{
    va_list arg;
    int bus, device, function;

    _pci_break_tag (tag, &bus, &device, &function);
    print_bdf (bus, device, function);

#ifdef __VARARGS_H
    va_start(arg);
#else
    va_start(arg, fmt);
#endif
    vkprintf (fmt, arg);
    va_end(arg);
}
//-----------------------------------------------------



//Scan each PCI device on the system and record its configuration requirements.

//===========================================================
static void
_pci_query_dev_func (unsigned int tag, int initialise)
{
    unsigned int id, pci_class;
    unsigned int old;
    unsigned int mask;
    unsigned int stat;
    unsigned int bparam;
    unsigned int x;
    int reg;
    struct pci_bus *pb;
    struct pcidev *pd;
    struct pciwin *pm, *pi;
    int bus, device, function;

    char devinfo[256];
	int supported;

    pci_class = _pci_conf_read(tag, PCI_CLASS_REG);

    id = _pci_conf_read(tag, PCI_ID_REG);

    // to find if the corresponding device info exists.
    if (_pci_devinfo_func) {
    	(*_pci_devinfo_func)(id, pci_class, devinfo, &supported);
    	_pci_tagprintf (tag, "%s\n", devinfo);
    }

    if (pcindev >= dimof(pcidevs)) {
    	kprintf ("pci: too many devices\n");
    	return;
    }

    pd = &pcidevs[pcindev++];

    _pci_break_tag (tag, &bus, &device, &function);

    pd->pa.pa_bus = bus;
    pd->pa.pa_device = device;
    pd->pa.pa_function = function;
    pd->pa.pa_tag = tag;
    pd->pa.pa_id = id;
    pd->pa.pa_class = pci_class;
    pd->nmemwin = 0;
    pd->niowin = 0;

    stat = _pci_conf_read(tag, PCI_COMMAND_STATUS_REG);

    pb = &_pci_bus[bus];
    pb->ndev++;

    // do all devices support fast back-to-back
    if ((stat & PCI_STATUS_BACKTOBACK_OKAY) == 0)
	    pb->fast_b2b = 0;		// no, sorry

    // do all devices run at 66 MHz
    if ((stat & PCI_STATUS_66MHZ_OKAY) == 0)
	    pb->freq66 = 0; 		// no, sorry

    // find slowest devsel
    x = (stat & PCI_STATUS_DEVSEL_MASK) >> PCI_STATUS_DEVSEL_SHIFT;
    if (x > pb->devsel)
	    pb->devsel = x;

    bparam = _pci_conf_read(tag, PCI_BPARAM_REG);

    pd->min_gnt = PCI_BPARAM_MIN_GNT (bparam);
    pd->max_lat = PCI_BPARAM_MAX_LAT (bparam);

    if (pd->min_gnt != 0 || pd->max_lat != 0) {
	    // find largest minimum grant time of all devices
    	if (pd->min_gnt != 0 && pd->min_gnt > pb->min_gnt)
    	    pb->min_gnt = pd->min_gnt;

    	// find smallest maximum latency time of all devices
    	if (pd->max_lat != 0 && pd->max_lat < pb->max_lat)
    	    pb->max_lat = pd->max_lat;

    	if (pd->max_lat != 0)
    	    //subtract our minimum on-bus time per sec from bus bandwidth
    	    pb->bandwidth -= pd->min_gnt * 4000000 /
    		(pd->min_gnt + pd->max_lat);
    }

    if (PCI_CLASS(pci_class) == PCI_CLASS_BRIDGE &&
	    PCI_SUBCLASS(pci_class) == PCI_SUBCLASS_BRIDGE_PCI)
	        return;

    //----------------------------------------------------------------------------------
    // bit0 = 0 :  mem space
    //	    = 1 : I/O space
    // after a writen all-"1", followed by a read operation, the space size can be gain,
    // then we have to write the old value back to restore the original state.
    //----------------------------------------------------------------------------------
    for (reg = PCI_MAP_REG_START; reg < PCI_MAP_REG_END; reg += 4) {
    	old = _pci_conf_read(tag, reg);
    	_pci_conf_write(tag, reg, 0xfffffffe);
    	mask = _pci_conf_read(tag, reg);    // from "mask" should be able to calculate  the responding space size
    										//for the io space, mask's bit 0 should be '1'; while for mem space, should be '0'
    	_pci_conf_write(tag, reg, old);

    	if (mask == 0 || mask == 0xffffffff)
    	    break;

    	if ((mask & PCI_MAP_IO) != 0) {
    	    mask |= 0xffff0000; // must be ones

    	    if (pciniowin >= dimof(pciiowin)) {
        		kprintf ("pci: too many i/o windows\n");
        		continue;
    	    }

    	    pi = &pciiowin[pciniowin++];

    	    pi->dev = pd;
    	    pi->reg = reg;
    	    pi->size = -((int)(mask & PCI_MAP_IO_ADDRESS_MASK));
    	    pd->niowin++;
    	}

    	else {
    	    switch (mask & PCI_MAP_MEMORY_TYPE_MASK) {
        	    case PCI_MAP_MEMORY_TYPE_32BIT:
        	    case PCI_MAP_MEMORY_TYPE_32BIT_1M:
        		    break;
        	    case PCI_MAP_MEMORY_TYPE_64BIT:
        		    _pci_tagprintf (tag, "64-bit region ignored\n");
        		    continue;
        	    default:
        		    _pci_tagprintf (tag, "reserved mapping type 0x%x\n",
        				 mask & PCI_MAP_MEMORY_TYPE_MASK);
        		    continue;
    	    }

    	    if  ((mask & PCI_MAP_MEMORY_PREFETCHABLE) == 0)
    		    _pci_bus[bus].prefetch = 0;

    	    if (pcinmemwin >= dimof(pcimemwin)) {
        		kprintf ("pci: too many memory windows\n");
        		continue;
    	    }

    	    pm = &pcimemwin[pcinmemwin++];

    	    pm->dev = pd;
    	    pm->reg = reg;
    	    pm->size = -((int)(mask & PCI_MAP_MEMORY_ADDRESS_MASK));
    	    pd->nmemwin++;
    	}
    }

    // Finally check for Expansion ROM
    reg = PCI_MAP_REG_ROM;
    old = _pci_conf_read(tag, reg);
    _pci_conf_write(tag, reg, 0xfffffffe);
    mask = _pci_conf_read(tag, reg);
    _pci_conf_write(tag, reg, old);

    if (mask != 0 && mask != 0xffffffff) {
    	if (pcinmemwin >= dimof(pcimemwin)) {
    	    kprintf ("pci: too many memory windows\n");
    	    return;
    	}

    	pm = &pcimemwin[pcinmemwin++];
    	pm->dev = pd;
    	pm->reg = reg;
    	pm->size = -((int)(mask & PCI_MAP_ROM_ADDRESS_MASK));
    	pd->nmemwin++;
    }
}
//---------------------------------------------------------



//===============================================================
static void
_pci_query_dev (int bus, int device, int initialise)
{
    unsigned int tag;
    unsigned int id;
    unsigned int misc;
    int hdrtype;

    int function;

    tag = _pci_make_tag(bus, device, 0);

    id = _pci_conf_read(tag, PCI_ID_REG);

    if (id == 0 || id == 0xffffffff)
	return;

    kprintf("found PCI device,id=0x%x\n",id);
    misc = _pci_conf_read(tag, PCI_MISC_REG);

    hdrtype = PCI_MISC_HDRTYPE(misc);

    if (hdrtype & PCI_MISC_HDRTYPE_MULTI) {
       	kprintf("----multifunc-----\n");
    	for (function = 0; function < PCI_FUNCMAX; function++) {
    	    tag = _pci_make_tag(bus, device, function);
    	    id = _pci_conf_read(tag, PCI_ID_REG);
    	    if (id == 0 || id == 0xffffffff)
    		continue;
    	    _pci_query_dev_func (tag, initialise);
    	}
    }
    else
	{
	    _pci_query_dev_func (tag, initialise);
    }
}
//------------------------------------------------------------------


static int
wincompare (struct pciwin  *a, struct pciwin *b)
{
    struct pciwin *wa = a;
    struct pciwin *wb = b;

    if (wa->dev->pa.pa_bus != wb->dev->pa.pa_bus)
	// sort into ascending order of bus number
	return (int)(wa->dev->pa.pa_bus - wb->dev->pa.pa_bus);
    else
	// sort into descending order of size
	return (int)(wb->size - wa->size);
}


static void
_pci_setup_iowins (void)
{
    struct pciwin *pi;
    struct pcidev *pd;
    struct pci_bus *pb;
    unsigned int base;

	kprintf("_pci_setup_iowins starting!!\n");
	kprintf("pciniowin=%d\n",pciniowin);

    qsort((char *)pciiowin, pciniowin, sizeof (struct pciwin), wincompare);

    for (pi = pciiowin; pi < &pciiowin[pciniowin]; pi++) {

    	pd = pi->dev;

    	if (pd->niowin < 0)
    	    continue;

    	pi->address = _pci_allocate_io (pd->pa.pa_tag, pi->size);

    	if (pi->address == (unsigned int) -1) {
    	    _pci_tagprintf (pd->pa.pa_tag,
    			    "not enough PCI i/o space (%d requested)\n",
    			    pi->size);
    	    pd->nmemwin = pd->niowin =  (short)-1;
    	    continue;
    	}

    	pb = &_pci_bus[pd->pa.pa_bus];
    	if (pi->address < pb->min_io_addr)
    	    pb->min_io_addr = pi->address;

    	if (pi->address + pi->size - 1 > pb->max_io_addr)
    	    pb->max_io_addr = pi->address + pi->size - 1;

    	base = _pci_conf_read(pd->pa.pa_tag, pi->reg);
    	base = (base & ~PCI_MAP_IO_ADDRESS_MASK) | pi->address;
    	_pci_conf_write(pd->pa.pa_tag, pi->reg, base);
    }
}

static void
_pci_setup_memwins (void)
{
    struct pciwin *pm;
    struct pcidev *pd;
	struct pci_bus *pb;
    unsigned int base;

	kprintf("in _pci_setup_memwins pcinmemwin=%d\n",pcinmemwin);

	qsort((char *)pcimemwin, pcinmemwin, sizeof (struct pciwin), wincompare);

    for (pm = pcimemwin; pm < &pcimemwin[pcinmemwin]; pm++) {

    	pd = pm->dev;

    	if (pd->nmemwin < 0)
    	    continue;

    	pm->address = _pci_allocate_mem (pd->pa.pa_tag, pm->size);

    	if (pm->address == (unsigned int)-1) {
    	    _pci_tagprintf (pd->pa.pa_tag,
    			    "not enough PCI mem space (%d requested)\n",
    			    pm->size);
    	    pd->nmemwin = pd->niowin = (short)-1;
    	    continue;
    	}

    	pb = &_pci_bus[pd->pa.pa_bus];

    	if (pm->address < pb->min_mem_addr)
    	    pb->min_mem_addr = pm->address;

    	if (pm->address + pm->size - 1 > pb->max_mem_addr)
    	    pb->max_mem_addr = pm->address + pm->size - 1;

    	if (pm->reg != PCI_MAP_REG_ROM) {
    	    // normal memory - expansion rom done below
    	    base = _pci_conf_read(pd->pa.pa_tag, pm->reg);
    	    base = pm->address | (base & ~PCI_MAP_MEMORY_ADDRESS_MASK);
    	    _pci_conf_write(pd->pa.pa_tag, pm->reg, base);
    	}
    }

    // Program expansion rom address base after normal memory base,
    //   to keep DEC ethernet chip happy
    for (pm = pcimemwin; pm < &pcimemwin[pcinmemwin]; pm++) {

    	if (pm->reg == PCI_MAP_REG_ROM && pm->address != (unsigned int)-1) {
    	    /* expansion rom */
    	    pd = pm->dev;
    	    _pci_conf_write(pd->pa.pa_tag, pm->reg, pm->address | PCI_MAP_ROM);
    	}
    }
	kprintf("leaving the pci_mem_setup\n");
}


//#pragma weak _pci_cacheline_log2
int
_pci_cacheline_log2 (void)
{
    return 3; // default to 8 words == 2^3
}


static void
_pci_setup_devices (int initialise)
{
    struct pcidev *pd;
    struct pci_bus *pb;

    unsigned int tag;
    unsigned int cmd, misc;
    unsigned int ltim;

	if (initialise) {
		// Enable each PCI pci_interface
		for (pd = pcidevs; pd < &pcidevs[pcindev]; pd++) {

			pb = &_pci_bus[pd->pa.pa_bus];
			tag = pd->pa.pa_tag;

    		if (pd->pa.pa_device != 15) {   // if pd->pa.pa_device == 15, the device should be pci2050 pci-bridge
    			kprintf("pa_device is %d \n", pd->pa.pa_device);
    		    cmd = _pci_conf_read(tag, PCI_COMMAND_STATUS_REG);
    		    cmd |= PCI_COMMAND_MASTER_ENABLE | PCI_COMMAND_SERR_ENABLE	| PCI_COMMAND_PARITY_ENABLE;    // SERR:system error reporting
    		    // always enable i/o & memory space, in case this card is
    			//   just snarfing space from the fixed ISA block and doesn't
    			//   declare separate PCI space.
    		    cmd |= PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE;

    			if (pb->fast_b2b)
    			    cmd |= PCI_COMMAND_BACKTOBACK_ENABLE;

    		    _pci_conf_write(tag, PCI_COMMAND_STATUS_REG, cmd);

    		    ltim = pd->min_gnt * (pb->freq66 ? 66 : 33) / 4;
    			ltim = MIN (MAX (pb->def_ltim, ltim), pb->max_ltim);

    			misc = _pci_conf_read (tag, PCI_MISC_REG);
    		    PCI_MISC_LTIM_SET (misc, ltim);
    		    PCI_MISC_CLSZ_SET (misc, 1 << _pci_cacheline_log2());

    			_pci_conf_write (tag, PCI_MISC_REG, misc);
    		}
		}
    }

    // Call the driver initialisation for each enabled device
    for (pd = pcidevs; pd < &pcidevs[pcindev]; pd++) {

		tag = pd->pa.pa_tag;

		kprintf("setup dev:pd->pa.pa_id=%x\n",pd->pa.pa_id);

		cmd = _pci_conf_read(tag, PCI_COMMAND_STATUS_REG);

		if (!initialise) {
		    // Wait for i/o & mem enable to be set (we may be in a race
			// with the system controller setting up the devices.
		    unsigned int retry = 100000;
		    while (!(cmd & (PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE))) {

				if (--retry == 0)   break;

				cmd = _pci_conf_read(tag, PCI_COMMAND_STATUS_REG);
		    }
		}

    	if (!(cmd & (PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE))) {

    	    continue;
    	}
    }
}


//===============================================================================================
//----------  main() --------------------------------
//====================================================================
void _pci_configure (int initialise)
{
    int bus, device;
    unsigned int def_ltim, max_ltim;

    struct pci_bus  *pb;

    _pci_devinfo_func = _pci_devinfo;


    kprintf("start PCI Configure!\n");
    // initialise the PCI bridge
    initialise = _pci_hwinit (initialise);
    if (initialise < 0)
	return;

    // for backwards compatibilty
    if (_pci_nbus == 0)
	_pci_nbus = 1;
    if (_pci_maxbus == 0) {
	_pci_maxbus = _pci_nbus;
	_pci_nbus = 1;
    }


    pcindev = pcinmemwin = pciniowin = 0;

    // scan configuration space of all devices
    for (bus = 0; bus < _pci_nbus; bus++) {

    	pb = &_pci_bus[bus];

    	for (device = 0; device <= PCI_DEVMAX; device++)
    	    _pci_query_dev (bus, device, initialise);

    	if (pb->ndev == 0)
    	    continue;

    	if (initialise) {
    	    // convert largest minimum grant time to cycle count
    	    max_ltim = pb->min_gnt * (pb->freq66 ? 66 : 33) / 4;

    	    // now see how much bandwidth is left to distribute
    	    if (pb->bandwidth <= 0) {
        		_pci_bdfprintf (bus, -1, -1, "warning: total bandwidth exceeded\n");
        		def_ltim = 1;
    	    }

    	    else{
        		// calculate a fair share for each device
        		def_ltim = pb->bandwidth / pb->ndev;
        		if (def_ltim > pb->max_lat)
        		    // that would exceed critical time for some device
        		    def_ltim = pb->max_lat;
        		// convert to cycle count
        		def_ltim = def_ltim * (pb->freq66 ? 66 : 33) / 4;
    	    }

    	    // most devices don't implement bottom three bits, so round up
    	    def_ltim = (def_ltim + 7) & ~7;
    	    max_ltim = (max_ltim + 7) & ~7;

    	    pb->def_ltim = MIN (def_ltim, 255);
    	    pb->max_ltim = MIN (MAX (max_ltim, def_ltim), 255);
    	}
    }

    if (initialise) {
    	// alter PCI bridge parameters based on query data
    	_pci_hwreinit ();

    	// setup the individual device windows
    	_pci_setup_iowins ();
    	_pci_setup_memwins ();
    	kprintf("pci_nbus is %d \n", _pci_nbus);
    	//if (_pci_nbus > 1)
    	//    _pci_setup_ppb ();
    }

    // enable each device and call its initialisation routine
    _pci_setup_devices (initialise);
    _pci_enumerated = 1;
}
//-------------------------------------------------------------------

#if 1

struct linux_pci_bus oklinuxpcibus;
struct pci_dev oklinuxpcidev;
struct pci_dev * linuxdev = &oklinuxpcidev;

//Search in the pmon style pci device,then change it to linux style
struct pci_dev * pci_find_class(unsigned int pci_class)
{
	struct pcidev *pd;
	unsigned long temp;

	for (pd = pcidevs; pd < &pcidevs[pcindev]; pd++) {

		kprintf("pd->pci_class=0x%x\n",pd->pa.pa_class);
		temp=pd->pa.pa_class;
		temp>>=8;

		if (temp == pci_class){
			linuxdev->vendor = (unsigned short)(pd->pa.pa_id&0xffff);
			linuxdev->device = (unsigned short)((pd->pa.pa_id>>16)&0xffff);
			linuxdev->pci_class  = pd->pa.pa_class&0xffffff;
			linuxdev->pmontag = pd->pa.pa_tag;
			linuxdev->bus=&oklinuxpcibus;
			linuxdev->bus->number=pd->pa.pa_bus;
			linuxdev->devfn = ((pd->pa.pa_device&0x1f)<<3)|(pd->pa.pa_function&0x7);
			return linuxdev;
		}

	}
	return 0;
}


void pcibios_read_config_dword(int bus,int devfn,int offset,int * val)
{
	*val=_pci_conf_read(_pci_make_tag(bus,((devfn>>3)&0x1f),(devfn&0x7)),offset);
}

void pcibios_write_config_dword(int bus,int devfn,int offset,int val)
{
	_pci_conf_write(_pci_make_tag(bus,((devfn>>3)&0x1f),(devfn&0x7)),offset,val);
}

#endif
